package net.guerlab.smart.wx.service.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import me.chanjar.weixin.common.error.WxErrorException;
import net.guerlab.commons.exception.ApplicationException;
import net.guerlab.smart.platform.commons.Constants;
import net.guerlab.smart.wx.core.domain.LoginResponse;
import net.guerlab.smart.wx.core.domain.MaEncryptedData;
import net.guerlab.smart.wx.core.exception.*;
import net.guerlab.smart.wx.miniapp.spring.exception.WxMiniAppCheckUserInfoFailException;
import net.guerlab.smart.wx.miniapp.spring.exception.WxMiniAppSessionKeyInvalidException;
import net.guerlab.smart.wx.miniapp.spring.storage.ISessionKeyStorage;
import net.guerlab.smart.wx.service.entity.WxApp;
import net.guerlab.smart.wx.service.entity.WxUser;
import net.guerlab.smart.wx.service.service.WxMaLoginService;
import net.guerlab.smart.wx.service.service.WxMaManagerService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * 微信小程序登录服务实现
 *
 * @author guer
 */
@Service
public class WxMaLoginServiceImpl extends AbstractWxLoginServiceImpl implements WxMaLoginService {

    private WxMaManagerService managerService;

    private ISessionKeyStorage sessionKeyStorage;

    @Override
    public LoginResponse login(String appId, String code, String ip, String referer) {
        if (StringUtils.isBlank(code)) {
            throw new CodeInvalidException();
        }

        WxApp wxApp = getWxApp(appId);
        WxMaUserService wxMaUserService = getWxMaService(appId).getUserService();
        WxMaJscode2SessionResult session;

        try {
            session = wxMaUserService.getSessionInfo(code);
        } catch (WxErrorException e) {
            throw new ApplicationException(e.getError().getErrorMsg(), e.getError().getErrorCode());
        }

        String openId = session.getOpenid();
        String unionId = session.getUnionid();

        sessionKeyStorage.put(appId, openId, session.getSessionKey());

        WxUser wxUser = wxUserService.findUser(appId, openId);
        if (wxUser == null) {
            LocalDateTime now = LocalDateTime.now();
            wxUser = new WxUser();
            wxUser.setOpenId(openId);
            wxUser.setAppId(appId);
            wxUser.setAppName(wxApp.getAppName());
            wxUser.setUnionId(unionId);
            wxUser.setAvatarUrl(Constants.EMPTY_NAME);
            wxUser.setNickName(Constants.EMPTY_NAME);
            wxUser.setRegistryTime(now);
            wxUser.setLastLoginTime(now);
            wxUser.setActivated(false);
            wxUserService.insert(wxUser);
            wxUserLoginLogService.addLog(wxUser, ip, referer);
        } else {
            updateUserInfo(wxUser, openId, unionId, false, ip, referer);
        }

        return buildLoginResponse(wxUser);
    }

    @Override
    public LoginResponse register(String appId, String openId, MaEncryptedData encryptedData) {
        if (StringUtils.isBlank(appId)) {
            throw new AppIdInvalidException();
        }
        if (StringUtils.isBlank(openId)) {
            throw new OpenIdInvalidException();
        }
        if (encryptedData == null) {
            throw new MaEncryptedDataInvalidException();
        }

        String sessionKey = sessionKeyStorage.get(appId, openId);
        if (StringUtils.isBlank(sessionKey)) {
            throw new WxMiniAppSessionKeyInvalidException();
        }

        WxMaUserService wxMaUserService = getWxMaService(appId).getUserService();

        if (!wxMaUserService.checkUserInfo(sessionKey, encryptedData.getRawData(), encryptedData.getSignature())) {
            throw new WxMiniAppCheckUserInfoFailException();
        }

        WxMaUserInfo userInfo = wxMaUserService
                .getUserInfo(sessionKey, encryptedData.getEncryptedData(), encryptedData.getIv());

        WxUser wxUser = wxUserService.findUser(appId, openId);

        LocalDateTime now = LocalDateTime.now();
        if (wxUser == null) {
            WxApp wxApp = wxAppService.selectByIdOptional(appId).orElseThrow(WxAppInvalidException::new);

            wxUser = new WxUser();
            wxUser.setOpenId(openId);
            wxUser.setAppId(appId);
            wxUser.setAppName(wxApp.getAppName());
            wxUser.setUnionId(userInfo.getUnionId());
            wxUser.setAvatarUrl(userInfo.getAvatarUrl());
            wxUser.setNickName(userInfo.getNickName());
            wxUser.setActivated(true);
            wxUserService.insertSelective(wxUser);
        } else {
            WxUser update = new WxUser();
            update.setOpenId(openId);
            update.setAppId(wxUser.getAppId());
            update.setUnionId(userInfo.getUnionId());
            update.setAvatarUrl(userInfo.getAvatarUrl());
            update.setNickName(userInfo.getNickName());
            update.setLastLoginTime(now);
            update.setVersion(wxUser.getVersion());
            update.setActivated(true);

            wxUserService.updateSelectiveById(update);

            wxUser.setUnionId(userInfo.getUnionId());
            wxUser.setAvatarUrl(userInfo.getAvatarUrl());
            wxUser.setNickName(userInfo.getNickName());
            wxUser.setLastLoginTime(now);
            wxUser.setActivated(true);
        }

        return buildLoginResponse(wxUser);
    }

    private WxMaService getWxMaService(String appId) {
        return managerService.getService(appId);
    }

    @Autowired
    public void setManagerService(WxMaManagerService managerService) {
        this.managerService = managerService;
    }

    @Autowired
    public void setSessionKeyStorage(ISessionKeyStorage sessionKeyStorage) {
        this.sessionKeyStorage = sessionKeyStorage;
    }
}
